from absl import app, flags
import json
import math
import datetime
import time
import memcache
import pandas as pd

from coin.base.param_util import split_string
from coin.util.protobuf.util import protos_to_df
from xunkemgmt_client.client.util.query_util import (
    query_accounts,
    query_strategies,
    query_applications,
    query_exchange_rates,
    query_balance_snapshots)
from xunkemgmt_client.tool.slack_noti import send_to_slack

FLAGS = flags.FLAGS


def _create_balance_dataframe(response_list):
  balance_list = []
  for response in response_list:
    if response:
      meta_dict = {
          'market_type': response.balance_info.market_type,
          'exchange': response.balance_info.exchange,
          'owner': response.balance_info.owner
      }
      currency_dict = {balance.currency: balance.total for balance in response.each_balance
                        if not math.isclose(balance.total, 0, abs_tol=1e-5)}
      balance_list.append({**meta_dict, **currency_dict})
    else:
      balance_list.append(None)
  return pd.DataFrame(balance_list)


def get_exchange_fee_coin():
  quote_coin_map = {
    'USD': ['QASH'],
    'USDT': ['BNB', 'HT']
  }
  coin_price_dict = {}
  for quote, coins in quote_coin_map.items():
    price = query_exchange_rates(coins, quote)
    coin_price_dict.update(dict(zip(coins, price)))
  exchange_coin_fee = {
      'Spot': {
          'Okex': ['OKDK', 1],
          'Binance': ['BNB', coin_price_dict['BNB']],
          'Huobi': ['HT', coin_price_dict['HT']],
          'Quoinex': ['QASH', coin_price_dict['QASH']]
      },
      'Futures': {
          'Binance': ['BNB', coin_price_dict['BNB']],
      }
  }
  return exchange_coin_fee


class Memdb(object):
  def __init__(self):
    self._mc = memcache.Client(["127.0.0.1:11211"])

  def dump_dt(self, key):
    dt_str = self._mc.get(key)
    if dt_str is None:
      self.store_dt(key)
      return datetime.datetime.utcnow()
    else:
      dt = datetime.datetime.strptime(dt_str, "%Y%m%d %H:%M:%S")
      return dt

  def store_dt(self, key):
    dt = datetime.datetime.utcnow()
    dt_str = dt.strftime('%Y%m%d %H:%M:%S')
    self._mc.set(key, dt_str, time=86400)


def _query_balance(request_list, source_type):
  if source_type == 'mysql':
    query_ts = int(time.time() * 10 ** 9)
  elif source_type == 'memcached':
    query_ts = None
  else:
    raise ValueError('Unknown source_type: %s' % source_type)
  account_ids = [req.account_id for req in request_list]
  meta_info = ["market_type", "exchange", "owner"]
  response_list = query_balance_snapshots(query_ts=query_ts,
                                          account_ids=account_ids,
                                          as_proto=True)
  response_df = _create_balance_dataframe(response_list)
  request_df = protos_to_df(request_list)
  df = request_df.merge(response_df, on=meta_info)
  df = df.sort_values(meta_info)
  return df


def _get_threshold(alarm_level, threshold=None):
  if alarm_level == 'warning':
    threshold = 1
  elif alarm_level == 'info':
    threshold = 200
  elif alarm_level == 'daily_info':
    threshold = threshold
  else:
    raise ValueError("Wrong alarm level")
  return threshold


def get_all_application_detail():
  application_df = query_applications()
  application_df = application_df[['account_id', 'strategy_id', 'application_id']]
  strategy_df = query_strategies()
  account_df = query_accounts(active=True)
  application_detail = pd.merge(account_df, application_df, on='account_id')
  application_detail = pd.merge(application_detail, strategy_df, on='strategy_id')
  return application_detail


def _check_fee_coin(balance_info, market_type, exchange, owner, threshold, trader):
  if exchange == 'Okex':
    return None
  exchange_coin_fee = get_exchange_fee_coin()
  fee_coin, fee_coin_in_usdt = exchange_coin_fee[market_type][exchange]
  threshold_num = threshold / fee_coin_in_usdt
  account_balance = balance_info[(balance_info['market_type'] == market_type)
                                 & (balance_info['exchange'] == exchange) &
                                 (balance_info['owner'] == owner)]
  if len(account_balance) == 0: return None
  account_balance = account_balance.fillna(0)
  diff_num = threshold_num - account_balance[fee_coin].iloc[0]
  return [
      market_type,
      exchange,
      trader,
      owner,
      fee_coin,
      threshold,
      round(account_balance[fee_coin].iloc[0], 2),
      round(threshold_num, 2),
      round(diff_num, 2)
  ]


def report(alarm_level, slack_receiver):
  print("alarm level: ", alarm_level)
  request_list = query_accounts(active=True,
                                business_units=['Coin'],
                                account_groups=split_string(FLAGS.account_group),
                                as_proto=True)
  balance_info = _query_balance(request_list, FLAGS.source_type)
  missing_balance_info = [req for req in request_list
    if req.account_id not in list(balance_info['account_id'])]
  missing_balance_info = protos_to_df(missing_balance_info)
  if len(missing_balance_info) > 0:
    missing_balance_info = missing_balance_info[['market_type', 'exchange', 'owner']]
    print(f'missing accounts without balances:\n{missing_balance_info.to_string()}')
  application_detail = get_all_application_detail()
  application_detail.set_index(['market_type', 'exchange', 'owner'], inplace=True)
  with open(FLAGS.coin_fee_threshold_file) as f:
    coin_fee_infos = json.loads(f.read())
  check_list = []
  for market_type in coin_fee_infos:
    coin_fee_infos_sub = coin_fee_infos[market_type]
    for coin_fee_info in coin_fee_infos_sub:
      exchange = coin_fee_info['exchange']
      owner = coin_fee_info['owner']
      if (market_type, exchange, owner) not in application_detail.index:
        continue
      trader_info = application_detail.loc[(market_type, exchange, owner), 'trader']
      trader = ','.join(list(trader_info)) if isinstance(trader_info, pd.Series) else trader_info
      threshold = coin_fee_info['threshold_in_usdt']
      threshold = _get_threshold(alarm_level, threshold)
      result = _check_fee_coin(balance_info, market_type, exchange, owner, threshold, trader)
      check_list.append(result)
  check_list = [x for x in check_list if x is not None]
  assert len(check_list) > 0
  check_df = pd.DataFrame(check_list,
                          columns=[
                              'market_type',
                              'exchange',
                              'trader',
                              'owner',
                              'fee_coin',
                              'threshold_in_usdt',
                              'fee_coin_num',
                              'threshold_num',
                              'deficit'
                          ])
  check_df.sort_values(['market_type', 'exchange', 'trader', 'owner', 'deficit'], inplace=True)
  print(check_df.to_string())
  check_df = check_df[check_df['deficit'] >= 0]
  if len(check_df) > 0 and slack_receiver is not None:
    traders = check_df['trader'].drop_duplicates().dropna().tolist()
    check_df.set_index(['market_type', 'exchange', 'trader', 'owner'], inplace=True)
    msg = check_df.to_string()
    msg = '```' + msg + '```'
    mention_list = traders if traders else None
    send_to_slack(msg, slack_receiver, 'msg', mention_list=mention_list)


def main(_):
  slack_receiver = FLAGS.slack_receiver
  mc = Memdb()
  mem_key = 'check_fee_coin'
  last_report_dt = mc.dump_dt(mem_key)
  curr_dt = datetime.datetime.utcnow()
  if curr_dt - last_report_dt > datetime.timedelta(minutes=FLAGS.info_alarm_frequency):
    report('info', slack_receiver)
    mc.store_dt(mem_key)
  if curr_dt.hour == 9 and curr_dt.minute <= 30:
    report('daily_info', slack_receiver)
  report('warning', slack_receiver)


if __name__ == '__main__':
  flags.DEFINE_string('account_group', 'Xunke', 'account group')
  flags.DEFINE_string('exchange', None, 'exchange')
  flags.DEFINE_string('source_type', 'memcached', 'mysql, memcached')
  flags.DEFINE_float('info_alarm_frequency', 360, 'minute')
  flags.DEFINE_string(
      'coin_fee_threshold_file',
      '../../coin_deploy/support_accounting/fee_coin_checker_config/coin_fee_threshold.json',
      'coin fee threshold file')
  flags.DEFINE_string(
      'memcached_config',
      "../../coin_deploy/support_accounting/db_config/memcached_config_prod.json",
      'path of database config.')
  flags.DEFINE_string(
      'mysql_config',
      "../../coin_deploy/support_accounting/db_config/mysql_config_prod.json",
      'path of database config.')
  flags.DEFINE_string('slack_receiver', None, 'channel to receive alarm msg')

  app.run(main)
